//============================================================================
// Name        : tutorial0.cpp
// Author      : Jamie LaPointe
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <boost/interprocess/shared_memory_object.hpp>
#include <boost/interprocess/mapped_region.hpp>
#include <boost/interprocess/sync/named_semaphore.hpp>
#include <boost/thread/thread.hpp>
#include <boost/bind.hpp>
#include <cstring>
#include <cstdlib>
#include <string>
#include <iostream>

struct SharedObject
{
   bool val0;
   int val1;
   float val2;
   double val3;
};

enum ProcessNum
{
   process1 = 1, process2
};

const std::string process1str = "process1";
const std::string process2str = "process2";
const char * const process2ReadyName = "process2Ready";
const char * const sharedMemoryName = "MySharedMemory";

const bool val0 = true;
const int val1 = 5;
const float val2 = 7.25;
const double val3 = 8.291321;

using namespace boost::interprocess;

// Starts process in a worker thread
void startProcess(ProcessNum processNum, std::string & nameOfMainProcess)
{
   //Launch child process
   std::string s(nameOfMainProcess);
   if (processNum == 1)
   {
      s += " " + process1str + " ";
   }
   else
   {
      s += " " + process2str + " ";
   }
   int retVal = std::system(s.c_str());
   if (retVal != 0)
   {
      std::cout << "ERROR: Child Process #" << processNum
            << "ended prematurely; retval = " << retVal << std::endl;
   }
}

// PRODUCER
void process1_work()
{
   std::cout << "Process 1 started" << std::endl;

   // Create a named semaphore to synchronize sharing of data with process2
   named_semaphore process2Ready(open_or_create, process2ReadyName, 0);

   // Init shared data with constant values to test in the other process
   SharedObject sharedObject;
   sharedObject.val0 = val0;
   sharedObject.val1 = val1;
   sharedObject.val2 = val2;
   sharedObject.val3 = val3;

   //Create a shared memory object.
   shared_memory_object shm(create_only, sharedMemoryName, read_write);

   //Set size
   shm.truncate(sizeof(SharedObject));

   //Map the whole shared memory in this process
   mapped_region region(shm, read_write);

   // copy the sharedObject into the shared memory space
   std::memcpy(region.get_address(), &sharedObject, region.get_size());

   //signal process2 that its data is ready for consumption.
   process2Ready.post();

   std::cout << "End of Process 1" << std::endl;
}

// CONSUMER
void process2_work()
{
   std::cout << "Process 2 started" << std::endl;

   // wait for process1 to produce and send its data to the shared memory location.
   named_semaphore process2Ready(open_or_create, process2ReadyName, 0);
   process2Ready.wait();

   std::cout << "Process1 signaled that data is ready for consumption"
         << std::endl;

   //Open already created shared memory object.
   shared_memory_object shm(open_only, sharedMemoryName, read_only);
   //Map the whole shared memory in this process
   mapped_region region(shm, read_only);

   // Map the object onto the shared memory.
   SharedObject *sharedObjectPtr =
         static_cast<SharedObject *> (region.get_address());

   // check that the object was mapped properly
   bool isValid = true;
   if (sharedObjectPtr->val0 != val0)
   {
      isValid = false;
   }
   else if (sharedObjectPtr->val1 != val1)
   {
      isValid = false;
   }
   else if (sharedObjectPtr->val2 != val2)
   {
      isValid = false;
   }
   else if (sharedObjectPtr->val3 != val3)
   {
      isValid = false;
   }

   if (isValid)
   {
      std::cout << "Memory is valid" << std::endl;
   }
   else
   {
      std::cout << "ERROR: Memory is invalid" << std::endl;
   }

   std::cout << "End of Process 2" << std::endl;
}

int main(int argc, char *argv[])
{
   if (argc == 1)
   {
      //Remove shared memory & named semaphore on construction and destruction
      // NOTE: it is VERY important that these be allowed to live for the lifetime
      //       of BOTH processes!!!!
      struct shm_remove
      {
         shm_remove()
         {
            shared_memory_object::remove(sharedMemoryName);
         }
         ~shm_remove()
         {
            shared_memory_object::remove(sharedMemoryName);
         }
      } remover;

      struct sem_remove
      {
         sem_remove()
         {
            named_semaphore::remove(process2ReadyName);
         }
         ~sem_remove()
         {
            named_semaphore::remove(process2ReadyName);
         }
      } semremover;

      // Create two non-blocking threads that will kick off process1 and process2,
      // then join the main thread and wait for the two processes to finish.
      // Due to the use of synchronization objects the order of execution for process1
      // and process2 is not important!
      boost::thread_group thrds;
      std::string nameOfMainProcess = std::string(argv[0]);
      thrds.create_thread(
            boost::bind(&startProcess, process1, nameOfMainProcess));
      thrds.create_thread(
            boost::bind(&startProcess, process2, nameOfMainProcess));
      thrds.join_all();
      std::cout << "All threads ended" << std::endl;
   }
   else if (strcmp(argv[1], process1str.c_str()) == 0)
   {
      // Kick off process1
      process1_work();
   }
   else if (strcmp(argv[1], process2str.c_str()) == 0)
   {
      // kick off process2
      process2_work();
   }
   else
   {
      // Invalid parameter sent to our main function.
      std::cout << "ERROR invalid parameter passed" << std::endl;
      return 1;
   }
   return 0;
}
